﻿#region Usings

using System;

#endregion

namespace Yandex.Market.Api.YmlSerialization
{
    /// <summary>
    ///   a wrapper class around user-defined types, for quick acess to their Yandex.Market.Api.YmlSerialization related attributes
    /// </summary>
    internal class UdtWrapper
    {
        #region Private Fields

        /// <summary>
        ///   boolean value indicating whether this instance is a wrapper around a collection type
        /// </summary>
        private readonly bool _isTypeCollection;

        /// <summary>
        ///   the underlying type for this instance of <c>TypeWrapper</c>
        /// </summary>
        private readonly Type _udtType = typeof(object);

        /// <summary>
        ///   reference to an instance of <c>EnumWrapper</c> in case that the current instance is an enum.
        /// </summary>
        private EnumWrapper _enumWrapper;

        /// <summary>
        ///   value indicating whether the serialization options has been explicitly adjusted
        ///   using attributes for the class
        /// </summary>
        private bool _isSerializationOptionSetByAttribute;

        #endregion

        #region Constructor

        /// <summary>
        ///   Initializes a new instance of the <see cref = "UdtWrapper" /> class.
        /// </summary>
        /// <param name = "udtType">The underlying type to create the wrapper around.</param>
        /// <param name = "callerSerializer">reference to the serializer 
        ///   instance which is building this instance.</param>
        public UdtWrapper(Type udtType, YmlSerializer callerSerializer)
        {
            _udtType = udtType;
            _isTypeCollection = ReflectionUtils.IsCollectionType(_udtType);

            Alias = ReflectionUtils.GetTypeFriendlyName(_udtType);
            Comment = null;
            FieldsToSerialize = YmlSerializationFields.PublicPropertiesOnly;
            IsAttributedAsNotCollection = false;

            SetYmlSerializerOptions(callerSerializer);

            foreach(var attr in _udtType.GetCustomAttributes(false))
            {
                if(attr is YmlBaseAttribute)
                    ProcessYmlAttribute(attr);
            }
        }

        #endregion

        #region Public Properties

        /// <summary>
        ///   Gets the alias of the type.
        /// </summary>
        /// <value>The alias of the type.</value>
        public string Alias { get; private set; }

        /// <summary>
        ///   Gets an array of comments for the underlying type.
        /// </summary>
        /// <value>The array of comments for the underlying type.</value>
        public string[] Comment { get; private set; }

        /// <summary>
        ///   Gets the fields to be serialized.
        /// </summary>
        /// <value>The fields to be serialized.</value>
        public YmlSerializationFields FieldsToSerialize { get; private set; }

        /// <summary>
        ///   Gets the serialization options.
        /// </summary>
        /// <value>The serialization options.</value>
        public YmlSerializationOptions SerializationOption { get; private set; }

        /// <summary>
        ///   Gets a value indicating whether this instance is attributed as not collection.
        /// </summary>
        /// <value>
        ///   <c>true</c> if this instance is attributed as not collection; otherwise, <c>false</c>.
        /// </value>
        public bool IsAttributedAsNotCollection { get; private set; }

        /// <summary>
        ///   Gets a value indicating whether this instance has comment.
        /// </summary>
        /// <value>
        ///   <c>true</c> if this instance has comment; otherwise, <c>false</c>.
        /// </value>
        public bool HasComment
        {
            get { return Comment != null && Comment.Length > 0; }
        }

        /// <summary>
        ///   Gets the underlying type corresponding to this wrapper.
        /// </summary>
        /// <value>The underlying type corresponding to this wrapper.</value>
        public Type UnderlyingType
        {
            get { return _udtType; }
        }

        /// <summary>
        ///   Gets a value indicating whether this instance wraps around an enum.
        /// </summary>
        /// <value><c>true</c> if this instance wraps around an enum; otherwise, <c>false</c>.</value>
        public bool IsEnum
        {
            get { return _udtType.IsEnum; }
        }

        /// <summary>
        ///   Gets the enum wrapper, provided that this instance wraps around an enum.
        /// </summary>
        /// <value>The enum wrapper, provided that this instance wraps around an enum.</value>
        public EnumWrapper EnumWrapper
        {
            get { return IsEnum ? (_enumWrapper ?? (_enumWrapper = new EnumWrapper(_udtType))) : null; }
        }

        /// <summary>
        ///   Determines whether serialization of null objects is not allowd.
        /// </summary>
        /// <returns>
        ///   <c>true</c> if serialization of null objects is not allowd; otherwise, <c>false</c>.
        /// </returns>
        public bool IsNotAllowdNullObjectSerialization
        {
            get { return SerializationOption == YmlSerializationOptions.DontSerializeNullObjects; }
        }

        /// <summary>
        ///   Gets a value indicating whether this instance wraps around a collection type.
        /// </summary>
        /// <value>
        ///   <c>true</c> if this instance wraps around a collection type; otherwise, <c>false</c>.
        /// </value>
        public bool IsCollectionType
        {
            get { return _isTypeCollection; }
        }

        /// <summary>
        ///   Gets a value indicating whether this instance is treated as collection.
        /// </summary>
        /// <value>
        ///   <c>true</c> if this instance is treated as collection; otherwise, <c>false</c>.
        /// </value>
        public bool IsTreatedAsCollection
        {
            get { return (!IsAttributedAsNotCollection && IsCollectionType); }
        }

        /// <summary>
        ///   Gets or sets the type of the custom serializer.
        /// </summary>
        /// <value>The type of the custom serializer.</value>
        public Type CustomSerializerType { get; private set; }

        /// <summary>
        ///   Gets a value indicating whether this instance has custom serializer.
        /// </summary>
        /// <value>
        ///   <c>true</c> if this instance has custom serializer; otherwise, <c>false</c>.
        /// </value>
        public bool HasCustomSerializer
        {
            get { return CustomSerializerType != null; }
        }

        #endregion

        #region Public Methods

        /// <summary>
        ///   Sets the serializer options.
        /// </summary>
        /// <param name = "caller">The caller serializer.</param>
        public void SetYmlSerializerOptions(YmlSerializer caller)
        {
            if(!_isSerializationOptionSetByAttribute)
                SerializationOption = caller != null
                                          ? caller.SerializationOption
                                          : YmlSerializationOptions.SerializeNullObjects;
        }

        /// <summary>
        ///   Returns a <see cref = "T:System.String" /> that represents the current <see cref = "T:System.Object" />.
        /// </summary>
        /// <returns>
        ///   A <see cref = "T:System.String" /> that represents the current <see cref = "T:System.Object" />.
        /// </returns>
        public override string ToString()
        {
            return _udtType.ToString();
        }

        /// <summary>
        ///   Determines whether the specified <see cref = "T:System.Object" /> is equal to the current <see cref = "T:System.Object" />.
        /// </summary>
        /// <param name = "obj">The <see cref = "T:System.Object" /> to compare with the current <see cref = "T:System.Object" />.</param>
        /// <returns>
        ///   true if the specified <see cref = "T:System.Object" /> is equal to the current <see cref = "T:System.Object" />; otherwise, false.
        /// </returns>
        /// <exception cref = "T:System.NullReferenceException">
        ///   The <paramref name = "obj" /> parameter is null.
        /// </exception>
        public override bool Equals(object obj)
        {
            if(obj is UdtWrapper)
            {
                UdtWrapper other = obj as UdtWrapper;
                return _udtType == other._udtType;
            }

            return false;
        }

        /// <summary>
        ///   Serves as a hash function for a particular type.
        /// </summary>
        /// <returns>
        ///   A hash code for the current <see cref = "T:System.Object" />.
        /// </returns>
        public override int GetHashCode()
        {
            return _udtType.GetHashCode();
        }

        #endregion

        #region Private Methods

        /// <summary>
        ///   Processes the specified attribute.
        /// </summary>
        /// <param name = "attr">The attribute to process.</param>
        private void ProcessYmlAttribute(object attr)
        {
            if(attr is YmlCommentAttribute)
            {
                string comment = (attr as YmlCommentAttribute).Comment;
                if(!String.IsNullOrEmpty(comment))
                {
                    string[] comments = comment.Split(new[] {'\r', '\n'}, StringSplitOptions.RemoveEmptyEntries);
                    for(int i = 0; i < comments.Length; i++)
                        comments[i] = String.Format(" {0} ", comments[i].Trim());

                    Comment = comments;
                }
            }
            else if(attr is YmlSerializableTypeAttribute)
            {
                YmlSerializableTypeAttribute theAttr = attr as YmlSerializableTypeAttribute;
                FieldsToSerialize = theAttr.FieldsToSerialize;
                if(theAttr.IsSerializationOptionSet())
                {
                    SerializationOption = theAttr.Options;
                    _isSerializationOptionSetByAttribute = true;
                }
            }
            else if(attr is YmlSerializeAsAttribute)
                Alias = (attr as YmlSerializeAsAttribute).SerializeAs;
            else if(attr is YmlNotCollectionAttribute)
            {
                if(!ReflectionUtils.IsArray(_udtType))
                    IsAttributedAsNotCollection = true;
            }
            else if(attr is YmlCustomSerializerAttribute)
            {
                Type serType = (attr as YmlCustomSerializerAttribute).CustomSerializerType;

                Type genTypeArg;
                bool isDesiredInterface = ReflectionUtils.IsDerivedFromGenericInterfaceType(serType,
                                                                                            typeof(ICustomSerializer<>),
                                                                                            out genTypeArg);

                if(!isDesiredInterface)
                    throw new YmlException(
                        "The provided custom serialization type is not derived from the proper interface");
                if(genTypeArg != UnderlyingType)
                    throw new YmlException("The generic argument of the class and the type of the class do not match");
                CustomSerializerType = serType;
            }
            else
                throw new Exception("Attribute not applicable to types!");
        }

        #endregion
    }
}